package qbfiles;

import java.awt.Dialog;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.swing.JProgressBar;

import misc.CRCGenerator;
import res.KeyWords;

public class QbWriter implements Runnable{
  private ByteArrayOutputStream os;
  private String input;
  private Map<String, Integer> fieldMap;
  private int pos;
  private boolean unmarkedNumbers = false;
  private String test;
  private File output;
  private boolean isRunning = false;
  private JProgressBar progress = null;
  private Dialog owner;
  
  public QbWriter(String input, File output, Map<Integer, String> fields){
    os = new ByteArrayOutputStream();
    this.input = input;
    this.output = output;
    
    fieldMap = new HashMap<String, Integer>();
    Iterator<Map.Entry<Integer, String>> it = fields.entrySet().iterator();
    while (it.hasNext()) {
      Map.Entry<Integer, String> pair = (Map.Entry<Integer, String>)it.next();
      fieldMap.put(pair.getValue(), pair.getKey());
    }
  }
  
  public void run() {
    pos = 0;
    isRunning = true;
    
    try {
      while(nextToken() && isRunning);
      
      if(isRunning){
        createFieldsTable();
        
        FileOutputStream fos = new FileOutputStream(output);
        fos.write(os.toByteArray());
        fos.close();
      }
    }
    catch(Exception e){
      e.printStackTrace();
    }
    isRunning = false;
    owner.setVisible(false);
    owner.dispose();
  }
  
  public void cancel(){
    isRunning = false;
  }
  
  public void setOwner(Dialog owner){
    this.owner = owner;
  }
  
  public void setProgressBar(JProgressBar progress){
    this.progress = progress;
    progress.setMinimum(0);
    progress.setMaximum(input.length());
  }
  
  private boolean nextToken() throws IOException{
    if(progress != null){
      progress.setValue(pos);
      progress.repaint();
    }
    
    while(pos < input.length() && (input.charAt(pos) == '\n' || input.charAt(pos) == ' '))
      pos++;
    
    if(pos >= input.length())
      return false;
    
    if(input.length() > pos + 40)
      test = input.substring(pos, pos + 40);
    else
      test = input.substring(pos, input.length());
    
    if(test.indexOf(KeyWords.IF, 0) == 0)                        command(KeyWords.IF, 0x25);
    else if(test.indexOf(KeyWords.ELSE, 0) == 0)                 command(KeyWords.ELSE, 0x26);
    else if(test.indexOf(KeyWords.END_IF, 0) == 0)               command(KeyWords.END_IF, 0x28);
    else if(test.indexOf(KeyWords.NOT, 0) == 0)                  command(KeyWords.NOT, 0x39);
    else if(test.indexOf(KeyWords.AND, 0) == 0)                  command(KeyWords.AND, 0x33);
    else if(test.indexOf(KeyWords.OR, 0) == 0)                   command(KeyWords.OR, 0x32);
    else if(test.indexOf(KeyWords.LESS_OR_EQUAL, 0) == 0)        command(KeyWords.LESS_OR_EQUAL, 0x13);
    else if(test.indexOf(KeyWords.GREATER_OR_EQUAL, 0) == 0)     command(KeyWords.GREATER_OR_EQUAL, 0x15);
    else if(test.indexOf(KeyWords.LESS_THAN, 0) == 0)            command(KeyWords.LESS_THAN, 0x12);
    else if(test.indexOf(KeyWords.GREATER_THAN, 0) == 0)         command(KeyWords.GREATER_THAN, 0x14);
    
    else if(test.indexOf(KeyWords.EQUALS, 0) == 0)               command(KeyWords.EQUALS, 0x07);
    else if(test.indexOf(KeyWords.PLUS, 0) == 0)                 command(KeyWords.PLUS, 0x0b);
    else if(test.indexOf(KeyWords.DIVIDE, 0) == 0)               command(KeyWords.DIVIDE, 0x0d);
    else if(test.indexOf(KeyWords.MULTI, 0) == 0)                command(KeyWords.MULTI, 0x0c);
    
    else if(test.indexOf(KeyWords.FUNCTION, 0) == 0)             command(KeyWords.FUNCTION, 0x23);
    else if(test.indexOf(KeyWords.END_FUNCTION, 0) == 0)         command(KeyWords.END_FUNCTION, 0x24);
    else if(test.indexOf(KeyWords.LOOP, 0) == 0)                 command(KeyWords.LOOP, 0x20);
    else if(test.indexOf(KeyWords.END_LOOP, 0) == 0)             command(KeyWords.END_LOOP, 0x21);
    else if(test.indexOf(KeyWords.BREAK, 0) == 0)                command(KeyWords.BREAK, 0x22);
    else if(test.indexOf(KeyWords.RETURN, 0) == 0)               command(KeyWords.RETURN, 0x29);
    else if(test.indexOf(KeyWords.JUMP, 0) == 0)                 command(KeyWords.JUMP, 0x2e);
    else if(test.indexOf(KeyWords.NULL, 0) == 0)                 command(KeyWords.NULL, 0x2c);
    else if(test.indexOf(KeyWords.ARRAY, 0) == 0)                command(KeyWords.ARRAY, 0x05);
    else if(test.indexOf(KeyWords.VECTOR, 0) == 0)               command(KeyWords.VECTOR, 0x1e);
    else if(test.indexOf(KeyWords.PAIR, 0) == 0)                 command(KeyWords.PAIR, 0x1f);
    else if(test.indexOf(KeyWords.STRUCT, 0) == 0)               command(KeyWords.STRUCT, 0x03);
    else if(test.indexOf(KeyWords.RANDOM, 0) == 0)               command(KeyWords.RANDOM, 0x2f);
    else if(test.indexOf(KeyWords.RANDOM2, 0) == 0)              command(KeyWords.RANDOM2, 0x40);
    else if(test.indexOf(KeyWords.GLOBAL, 0) == 0)               command(KeyWords.GLOBAL, 0x2d);
    
    else if(test.indexOf(KeyWords.ROUND_BRACET_OPEN, 0) == 0)    command(KeyWords.ROUND_BRACET_OPEN);
    else if(test.indexOf(KeyWords.ROUND_BRACET_CLOSE, 0) == 0)   command(KeyWords.ROUND_BRACET_CLOSE, 0x06);
    else if(test.indexOf(KeyWords.SQUARED_BRACET_OPEN, 0) == 0){ command(KeyWords.SQUARED_BRACET_OPEN);  unmarkedNumbers = true;}
    else if(test.indexOf(KeyWords.SQUARED_BRACET_CLOSE, 0) == 0){command(KeyWords.SQUARED_BRACET_CLOSE); unmarkedNumbers = false;}
    else if(test.indexOf(KeyWords.CURLY_BRACET_OPEN, 0) == 0)    command(KeyWords.CURLY_BRACET_OPEN);
    else if(test.indexOf(KeyWords.CURLY_BRACET_CLOSE, 0) == 0)   command(KeyWords.CURLY_BRACET_CLOSE, 0x04);
    else if(test.indexOf(KeyWords.DIVIDER, 0) == 0)              command(KeyWords.DIVIDER);
    else if(test.indexOf(KeyWords.STRING_A, 0) == 0)
      os.write(parseString((byte)0x1B, KeyWords.STRING_A));
    else if(test.indexOf(KeyWords.STRING_B, 0) == 0)
      os.write(parseString((byte)0x1C, KeyWords.STRING_B));
    else if(test.indexOf("#", 0) == 0){
      os.write(0x02);
      os.write(parseLong(extractTokenAt(++pos)));
    }
    else if(test.indexOf("0x", 0) == 0)
      os.write(parseHex(extractTokenAt(pos)));
    else if(extractTokenAt(pos).matches("[-+]?[0-9]*\\.[0-9]+([eE][-+]?[0-9]+)?")){
      if(!unmarkedNumbers)
        os.write(0x1a);
      os.write(parseFloat(extractTokenAt(pos)));
    }
    else if(extractTokenAt(pos).matches("[-+]?[0-9]*")){
      if(!unmarkedNumbers)
        os.write(0x17);
      os.write(parseLong(extractTokenAt(pos)));
    }
    else if(test.indexOf(KeyWords.MINUS, 0) == 0)
      command(KeyWords.MINUS, 0x0a);
    else{
      os.write(parseField(extractTokenAt(pos)));
    }
    
    return true;
  }
  
  private void command(String word){
    pos += word.length();
  }
  
  private void command(String word, int b){
    command(word);
    os.write(b);
  }
  
  private String extractTokenAt(int pos){
    String token = "";
    
    if("-0123456789".indexOf(input.charAt(pos)) == -1)
      while(input.charAt(pos) != ' ' && input.charAt(pos) != '\n' && input.indexOf(KeyWords.DIVIDER, pos) != pos)
        token += input.charAt(pos++);
    
    else if(test.indexOf("0x", 0) == 0){
      while("0123456789xabcdef".indexOf(input.charAt(pos)) != -1)
        token += input.charAt(pos++);
    }
    else
      while("-0123456789.E".indexOf(input.charAt(pos)) != -1)
        token += input.charAt(pos++);
    
    return token;
  }
  
  private byte[] parseLong(String value){
    int number = Integer.parseInt(value);
    byte[] b = new byte[4];
    byte[] b2 = ByteBuffer.wrap(new byte[4]).putInt(number).array();
    b[0] = b2[3];
    b[1] = b2[2];
    b[2] = b2[1];
    b[3] = b2[0];
    
    pos += value.length();
    return b;
  }
  
  private byte[] parseFloat(String value){
    float number = Float.parseFloat(value);
    byte[] b = new byte[4];
    byte[] b2 = ByteBuffer.wrap(new byte[4]).putFloat(number).array();
    b[0] = b2[3];
    b[1] = b2[2];
    b[2] = b2[1];
    b[3] = b2[0];
    
    pos += value.length();
    return b;
  }
  
  private byte[] parseHex(String value){   
    byte[] result = new byte[value.length() / 2 - 1];
    
    for(int pointer = 2; (pointer + 1) < value.length(); pointer += 2){
      String hexByte = value.substring(pointer, pointer + 2);
      result[(pointer - 2) / 2] =
        (byte)((Character.digit(hexByte.charAt(0), 16) << 4) + 
            Character.digit(hexByte.charAt(1), 16));
    }
    
    pos += value.length();
    return result;
  }
  
  private byte[] parseString(byte marker, String representation){
    int start = ++pos;
    int end = -1;
    boolean endFound = false;
    while(!endFound){
      if(input.charAt(pos) == representation.charAt(0))
        endFound = true;
      pos++;
    }
    end = pos - 1;
    
    ByteBuffer result = ByteBuffer.wrap(new byte[end - start + 6]);
    result.put(marker);
    int oldPos = pos;
    result.put(parseLong(Long.toString(end - start + 1)));
    pos = oldPos;
    result.put((input.substring(start, end)).getBytes());
    result.put((byte)0x00);
    
    return result.array();
  }
  
  private Integer crc32(String value){
    return CRCGenerator.generateCRC(value);
  }
  
  private byte[] parseField(String value){
    byte[] result = new byte[5];
    
    if(!fieldMap.containsKey(value))
      fieldMap.put(value, crc32(value));
    result = ByteBuffer.wrap(result).put((byte)0x16).putInt(fieldMap.get(value)).array();
        
    pos += value.length();
    return result;
  }
  
  private void createFieldsTable() throws IOException{
    Iterator<Map.Entry<String, Integer>> it = fieldMap.entrySet().iterator();
    
    while (it.hasNext()) {
      Map.Entry<String, Integer> pair = (Map.Entry<String, Integer>)it.next();
      
      os.write(0x2b);
      os.write(ByteBuffer.wrap(new byte[4]).putInt(pair.getValue()).array());
      os.write(pair.getKey().getBytes());
      os.write(0x00);
    }
    os.write(0x00);
  }
}
